package com.upb.webserver.chain;

import cn.hutool.core.collection.CollectionUtil;
import com.upb.webserver.account.AccountService;
import com.upb.webserver.chainorg.ChainOrgService;
import com.upb.webserver.common.constant.Constant;
import com.upb.webserver.common.enums.*;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.base.BasePageResponse;
import com.upb.webserver.common.pojo.base.BaseResponse;
import com.upb.webserver.common.pojo.chain.ChainVO;
import com.upb.webserver.common.pojo.order.ChainUnionOrder;
import com.upb.webserver.common.pojo.order.ChainUnionOrderParam;
import com.upb.webserver.common.pojo.order.ReqAddWorkOrderOfNewChainVO;
import com.upb.webserver.common.pojo.rest.fisco.chainmgr.resp.RespChain;
import com.upb.webserver.common.pojo.rest.fisco.data.resp.ChainInfo;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.MybatisExampleTools;
import com.upb.webserver.company.CompanyDeveloperManager;
import com.upb.webserver.company.CompanyDeveloperService;
import com.upb.webserver.dao.entity.TbChain;
import com.upb.webserver.dao.entity.TbChainExample;
import com.upb.webserver.dao.entity.TbWorkOrder;
import com.upb.webserver.dao.mapper.TbChainMapper;
import com.upb.webserver.dao.mapper.TbChainOrgMapper;
import com.upb.webserver.dao.mapper.TbWorkOrderMapper;
import com.upb.webserver.order.OrderService;
import com.upb.webserver.order.WorkOrderManager;
import com.upb.webserver.org.OrgManager;
import com.upb.webserver.rest.fisco.FiscoChainMgrRestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.upb.webserver.common.constant.Constant.CHAIN_STATUS_DEPLOY_FINISH;
import static com.upb.webserver.common.constant.Constant.WORK_ORDER_STATUS_HANDLING;

@Slf4j
@Service
public class ChainService {
    @Autowired
    private AccountService accountService;
    @Autowired
    private TbWorkOrderMapper orderMapper;
    @Autowired
    private OrderService orderService;
    @Autowired
    private TbChainMapper chainMapper;
    @Autowired
    private WorkOrderManager workOrderManager;
    @Autowired
    private OrgManager orgManager;
    @Autowired
    private CompanyDeveloperService companyDeveloperService;
    @Autowired
    private CompanyDeveloperManager companyDeveloperManager;
    @Autowired
    private TbChainOrgMapper chainOrgMapper;
    @Autowired
    private ChainOrgService chainOrgService;
    @Autowired
    private FiscoChainMgrRestService fiscoChainMgrRestService;
    @Autowired
    private ChainManager chainManager;


    /**
     * @param chaiId
     * @return
     */
    public ChainVO getChainDetail(int chaiId) {
        log.info("start exec method [getChainDetail]. chaiId:{}", chaiId);

        //check chain status
        checkAllChainStatus();

        TbChain tbChain = chainManager.verifyChainId(chaiId);
        ChainVO chainVO = new ChainVO();
        BeanUtils.copyProperties(tbChain, chainVO);
        chainVO.setChainId(chaiId);
        chainVO.setCreatorAccountName(accountService.getAccountNameById(tbChain.getCreateFromPlatform(), tbChain.getCreateBy()));
        chainVO.setCreatorOrg(chainOrgService.getCreatorOrgName(tbChain.getCreateFromPlatform(), tbChain.getCreateBy(), tbChain.getId()));

        log.info("success exec method [getChainDetail]. result:{}", JsonUtils.objToString(chainVO));
        return chainVO;
    }

    /**
     * getChainInfoList.
     *
     * @return
     */
    public List<ChainInfo> getChainInfoList(Byte chainType) {
        List<ChainInfo> chainList = chainMapper.getChainInfoList(chainType);
        return chainList;
    }

    /**
     * @param platform
     * @param currentAccountId
     * @param inputChainStatusSet
     * @param pageNumber
     * @param pageSize
     * @return
     */
    public BasePageResponse queryChainByPage(Byte platform, int currentAccountId, String currentAccountRole, Set<Byte> inputChainStatusSet, int pageNumber, int pageSize) {
        log.info("start exec method [queryChainByPage]. platform:{} currentAccountId:{} currentAccountRole:{} pageNumber:{} pageSize:{} inputChainStatusSet:{}", platform, currentAccountId, currentAccountRole, pageNumber, pageSize, JsonUtils.objToString(inputChainStatusSet));

        //check chain status
        checkAllChainStatus();

        //param of chain union workOrder
        ChainUnionOrderParam param = initParamOfChainPage(platform, currentAccountId, currentAccountRole, inputChainStatusSet, pageNumber, pageSize);

        //query count
        BasePageResponse pageResponse = BasePageResponse.initialize();
        long chainCount;
        if ((chainCount = orderMapper.unionQueryCountFromChainAndWorkOrder(param)) == 0) {
            log.info("finish exec method [queryChainByPage], result:{}", JsonUtils.objToString(pageResponse));
            return pageResponse;
        }

        List<ChainVO> chainVOList = new ArrayList<>();

        //query by param
        List<ChainUnionOrder> chainUnionOrderList = orderMapper.unionQueryFromChainAndWorkOrder(param);
        Set<Integer> chainIdSet = chainUnionOrderList.stream().map(c -> c.getChainId()).filter(chainId -> chainId != null).collect(Collectors.toSet());
        List<TbChain> chainList = chainManager.getAllChainByIdSet(chainIdSet);
        for (TbChain chain : chainList) {
            //check chain status
            checkChainStatus(chain);

            ChainVO rspVo = new ChainVO();
            //copy properties
            BeanUtils.copyProperties(chain, rspVo);
            rspVo.setChainId(chain.getId());
            TbWorkOrder tbWorkOrder = orderMapper.selectByTargetTableAndTargetId(TableNameEnum.CHAIN.getName(), chain.getId());
            if (tbWorkOrder != null) {
                rspVo.setWorkOrderId(tbWorkOrder.getId());
            }
            rspVo.setCreatorAccountName(accountService.getAccountNameById(chain.getCreateFromPlatform(), chain.getCreateBy()));
            rspVo.setCreatorOrg(chainOrgService.getCreatorOrgName(chain.getCreateFromPlatform(), chain.getCreateBy(), chain.getId()));
            chainVOList.add(rspVo);
        }

        //query by workOrderId
        Set<Integer> orderIdSet = chainUnionOrderList.stream().map(c -> c.getWorkOrderId()).filter(orderId -> orderId != null).collect(Collectors.toSet());
        List<TbWorkOrder> workOrderList = workOrderManager.getAllWorkOrderByIdSet(orderIdSet);
        for (TbWorkOrder workOrder : workOrderList) {
            ChainVO chainVO = buildRspChainVO(platform, currentAccountId, workOrder);
            if (Objects.isNull(chainVO)) {
                continue;
            }
            chainVOList.add(chainVO);
        }

        pageResponse.setTotalCount(chainCount);
        pageResponse.setData(chainVOList.stream().sorted().collect(Collectors.toList()));
        log.info("success exec method [queryChainByPage]. platform:{} currentAccountId:{} pageNumber:{} pageSize:{} inputChainStatusSet:{} result:{}", platform, currentAccountId, pageNumber, pageSize, JsonUtils.objToString(inputChainStatusSet), JsonUtils.objToString(pageResponse));
        return pageResponse;
    }


    /**
     * @param platform
     * @param currentAccountId
     * @param inputChainStatusSet
     * @param pageNumber
     * @param pageSize
     * @return
     */
    public ChainUnionOrderParam initParamOfChainPage(Byte platform, int currentAccountId, String currentAccountRole, Set<Byte> inputChainStatusSet, int pageNumber, int pageSize) {
        log.info("start exec method [initParamOfChainPage]. platform:{} currentAccountId:{} currentAccountRole:{} pageNumber:{} pageSize:{} inputChainStatusSet:{}", platform, currentAccountId, currentAccountRole, pageNumber, pageSize, JsonUtils.objToString(inputChainStatusSet));

        //status of chain or workOrder
        Set<Byte> chainStatusSetOnDb = new HashSet<>();
        Set<Byte> workOrderStatusSetOnDb = new HashSet<>();
        for (Byte inputChainStatus : inputChainStatusSet) {
            if (Constant.CHAIN_OR_APP_STATUS_DEPLOY_SUCCESS.contains(inputChainStatus)) {
                chainStatusSetOnDb.add(inputChainStatus);
            } else {
                ChainOrAppStatusEnum chainOrAppStatusEnum = ChainOrAppStatusEnum.getById(inputChainStatus);
                if (null == chainOrAppStatusEnum) {
                    throw new WebServerException(ConstantCode.PARAM_EXCEPTION.attach(String.format("invalid inputChainStatus:%d", inputChainStatus)));
                }
                workOrderStatusSetOnDb.add(chainOrAppStatusEnum.convertToOrderStatusEnum().getValue());
            }
        }

        //all workOrderId about developer
        Set<Integer> workOrderIdSet = new HashSet<>();
        Set<Integer> chainIdIdSet = new HashSet<>();
        if (PlatformEnum.DEVELOPER.getId() == platform || RoleTypeEnum.API_MANAGER.getName().equalsIgnoreCase(currentAccountRole)) {

            List<Integer> companyIdList = null;
            if (RoleTypeEnum.API_MANAGER.getName().equalsIgnoreCase(currentAccountRole)) {
                companyIdList = companyDeveloperManager.listCompanyIdByPlatformAndAccountId(platform, currentAccountId);
            }

            if (PlatformEnum.DEVELOPER.getId() == platform) {
                int companyId = companyDeveloperService.selectCompanyIdByDeveloperId(currentAccountId);
                companyIdList = Arrays.asList(companyId);
            }

            //get set of workOrderId
            workOrderIdSet = workOrderManager.getAllWorkOrderOfNewChainByCompanyIdList(companyIdList);
            if (CollectionUtil.isEmpty(workOrderIdSet)) {
                workOrderIdSet = CollectionUtil.newHashSet(Integer.MIN_VALUE);
            }

            //get set of chainId
            chainIdIdSet = chainManager.getChainIdSetByCompanyIdList(companyIdList);
            if (CollectionUtil.isEmpty(chainIdIdSet)) {
                chainIdIdSet = CollectionUtil.newHashSet(Integer.MIN_VALUE);
            }
        }

        //param of chain union workOrder
        ChainUnionOrderParam param = new ChainUnionOrderParam();
        param.setChainStatusSet(chainStatusSetOnDb);
        param.setWorkOrderStatusSet(workOrderStatusSetOnDb);
        param.setWorkOrderIdSet(workOrderIdSet);
        param.setChainIdSet(chainIdIdSet);
        param.setPageNumber(pageNumber);
        param.setPageSize(pageSize);
        param.setSortTypeEnum(MysqlSortTypeEnum.DESC);

        log.info("success exec method [initParamOfChainPage]. result:{}", JsonUtils.objToString(param));
        return param;
    }


    /**
     *
     */
    public void checkAllChainStatus() {
        log.debug("start exec method [checkAllChainStatus].");
        int pageNumber = 0;
        while (true) {
            //next page
            pageNumber++;

            //param
            TbChainExample example = MybatisExampleTools.initSamplePageExample(TbChainExample.class, pageNumber, 50, null);
            TbChainExample.Criteria criteria = example.createCriteria();
            criteria.andCreateStatusNotEqualTo(ChainOrAppStatusEnum.RUNNING.getId());
            //query workOrderChain list
            List<TbChain> chainList = chainMapper.selectByExampleWithBLOBs(example);
            if (CollectionUtil.isEmpty(chainList)) {
                log.info("not found chain by pageNumber:{} pageSize:50", pageNumber);
                break;
            }

            //check chain status
            for (TbChain chain : chainList) {
                checkChainStatus(chain);
            }
        }


    }

    /**
     * @param chain
     */
    @Transactional
    private void checkChainStatus(TbChain chain) {
        //TODO 需要定时器自动查
        log.debug("start exec method [checkChainStatus]. chain:{}", JsonUtils.objToString(chain));
        if (!WORK_ORDER_STATUS_HANDLING.contains(chain.getCreateStatus())) {
            log.debug("finish  exec method [checkChainStatus]. chain is not in handling");
            return;
        }

        try {
            ChainOrAppStatusEnum chainStatus;
            if (ChainTypeEnum.FISCO.getId() == chain.getChainType()) {
                RespChain respChain = fiscoChainMgrRestService.getChainById(chain.getId());
                if (Objects.isNull(respChain)) {
                    log.warn("not found remote record by chainId:{}", chain.getId());
                    chainStatus = ChainOrAppStatusEnum.DEPLOY_FAIL;
                } else {
                    FiscoRemoteChainStatusEnum status = FiscoRemoteChainStatusEnum.getById(respChain.getChainStatus());
                    chain.setRemark(respChain.getRemark());
                    chainStatus = status.convertToChainOrAppStatusEnum();
                }

            } else {
                log.warn("fail method [checkChainStatus]. not support chainType:{}", chain.getChainType());
                return;
            }

            //update status of chain
            chain.setCreateStatus(chainStatus.getId());
            chain.setGmtModified(new Date());
            if (ChainOrAppStatusEnum.RUNNING.getId() == chainStatus.getId()) {
                chain.setRemark("success");
            }
            log.info("update chain status, chainId:{} to status:{}", chain.getId(), chain.getCreateStatus());
            chainMapper.updateByPrimaryKeyWithBLOBs(chain);

            //update status of workOrder
            if (CHAIN_STATUS_DEPLOY_FINISH.contains(chainStatus)) {
                //update workerStatus
                log.info("update workOrder status, workOrderId:{}  to status:{}", chain.getId(), chain.getCreateStatus());
                workOrderManager.updateStatusByTargetTableAndId(TableNameEnum.CHAIN, chain.getId(), chainStatus.convertToOrderStatusEnum(), chain.getRemark());
            }
        } catch (Exception ex) {
            log.warn("fail method [checkChainStatus]. chain:{}", JsonUtils.objToString(chain), ex);
            return;
        }
        log.debug("success exec method [checkChainStatus]. chain:{}", JsonUtils.objToString(chain));
    }


    /**
     * @param platform
     * @param currentAccountId
     * @param workOrder
     * @return
     */
    private ChainVO buildRspChainVO(Byte platform, int currentAccountId, TbWorkOrder workOrder) {

        if (null == workOrder) {
            log.warn("fail exec method[buildRspChainVO]. workOrder is null");
            return null;
        }

        ReqAddWorkOrderOfNewChainVO chainDetailVO = JsonUtils.objToJavaBean(workOrder.getDetail(), ReqAddWorkOrderOfNewChainVO.class);

        ChainVO chainVO = new ChainVO();
        BeanUtils.copyProperties(workOrder, chainVO);
        BeanUtils.copyProperties(chainDetailVO, chainVO);
        chainVO.setWorkOrderId(workOrder.getId());
        chainVO.setChainId(workOrder.getTargetId());
        chainVO.setCreatorAccountName(accountService.getAccountNameById(workOrder.getCreateFromPlatform(), workOrder.getCreateBy()));
        chainVO.setCreatorOrg(orderService.getCreatorOrgName(workOrder));
        chainVO.setHadSelfCompleted(orderService.getDeveloperCompleteStatus(platform, currentAccountId, workOrder));
        chainVO.setRemark(workOrder.getDescription());

        //        chainVO.setCreateDeveloperId(workOrder.getCreateBy());
//        chainVO.setCreateCompanyId(workOrder.getCreateBy());
//        Integer createOrgId = orderService.getCreaterOrg(workOrder);
//        if (Objects.nonNull(createOrgId)) {
//            chainVO.setCreateOrgId(createOrgId);
//        } else {
//            log.warn("not found createOrg, workOrder:{}", JsonUtils.objToString(workOrder));
//        }

        //create status
        ChainOrAppStatusEnum chainOrAppStatusEnum = OrderStatusEnum.getByValue(workOrder.getStatus()).convertToChainOrAppStatusEnum();
        if (chainOrAppStatusEnum != null) {
            chainVO.setCreateStatus(chainOrAppStatusEnum.getId());
        }
        chainVO.setRemark(workOrder.getDescription());
        return chainVO;
    }


    /**
     * @param chainType
     * @return
     */
    public BaseResponse queryImageTypeList(int chainType) {
        if (ChainTypeEnum.FISCO.getId() != chainType) {
            log.warn("fail exec method [queryImageTypeList] ,not support chainType:{}", chainType);
            throw new WebServerException(ConstantCode.CHAIN_TYPE_NOT_SUPPORT);
        }
        return fiscoChainMgrRestService.queryImageTypeList();
    }

}
